﻿using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using POC.Reports;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using wlog.Model;

namespace wlog.Shell.Storage.Blob
{
    public class BlobStorageCommand : StorageCommand
    {
      protected  string containerName;
      protected  string blobName;
      protected  bool async = false;
      protected  string filePath;
      protected  string mime = "text/plain";
      protected FileStream fileStream;
      bool fileOpened = false;


        protected  TransferTypeEnum TransferType = TransferTypeEnum.Download;
        public event AsyncCompletedEventHandler TransferCompleted;
        public event EventHandler<BlobTransferProgressChangedEventArgs> TransferProgressChanged;
        // Used to calculate download speeds
        private Queue<long> timeQueue = new Queue<long>(200);
        private Queue<long> bytesQueue = new Queue<long>(200);
        private DateTime updateTime = System.DateTime.Now;

        // Private BlobTransfer properties
       
        //private ICloudBlob m_Blob;

        private ICancellableAsyncResult asyncresult;
        protected bool Working = false;
        private object WorkingLock = new object();
        private AsyncOperation asyncOp;
        public override void Setup(string[] args)
        {
            base.Setup(args);
            containerName = objectName;
            blobName = additionalCommands[K_OPTIONS.objectname];
            filePath = additionalCommands[K_OPTIONS.objecturi];
            if (string.IsNullOrEmpty(additionalCommands[K_OPTIONS.asyncmode]))
            {
                async = false;
            }
            else
            {
                bool.TryParse(additionalCommands[K_OPTIONS.asyncmode], out async);
            }
            if (!string.IsNullOrEmpty(filePath))
            {
                string extn = Path.GetExtension(filePath);
                mime = FileLoaderExtn.GetMimeType(extn);
            }
        }
        public virtual void UploadSync(string filePath, ICloudBlob blob)
        {
            using (Stream file = File.Open(filePath, FileMode.Open, FileAccess.Read))
            {
                blob.UploadFromStream(file);
            }
        }
        public virtual void UploadAsync(string filePath, ICloudBlob blob)
        {
            lock (WorkingLock)
            {
                if (!Working)
                    Working = true;
                else
                    throw new Exception("BlobTransfer already initiated. Create new BlobTransfer object to initiate a new file transfer.");
            }
            // http://blogs.msdn.com/b/kwill/archive/2013/03/06/asynchronous-parallel-block-blob-transfers-with-progress-change-notification-2-0.aspx
            // Attempt to open the file first so that we throw an exception before getting into the async work
            if (!File.Exists(filePath))
            {
                throw new ArgumentNullException("No such file " + filePath);
            }

            // Create an async op in order to raise the events back to the client on the correct thread.
            asyncOp = AsyncOperationManager.CreateOperation(blob);
          
            var file = new FileInfo(filePath);
            long fileSize = file.Length;

            fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
            fileOpened = true;
            ProgressStream pstream = new ProgressStream(fileStream);
            pstream.ProgressChanged += pstream_ProgressChanged;
            pstream.SetLength(fileSize);
            blob.ServiceClient.ParallelOperationThreadCount = 10;
            asyncresult = blob.BeginUploadFromStream(pstream, BlobTransferCompletedCallback, new BlobTransferAsyncState(blob, pstream));

        }
        private void pstream_ProgressChanged(object sender,ProgressChangedEventArgs e)
        {
            BlobTransferProgressChangedEventArgs eArgs = null;
            int progress = (int)((double)e.BytesRead / e.TotalLength * 100);

            // raise the progress changed event on the asyncop thread
            eArgs = new BlobTransferProgressChangedEventArgs(e.BytesRead, e.TotalLength, progress, CalculateSpeed(e.BytesRead), null);
            asyncOp.Post(delegate(object e2) { OnTaskProgressChanged((BlobTransferProgressChangedEventArgs)e2); }, eArgs);
        }
        protected void EnsureEndUplaod(ICloudBlob blob, IAsyncResult result)
        {
            if (TransferType == TransferTypeEnum.Upload)
                blob.EndUploadFromStream(result);
        }
        protected void EnsureEndDownload(ICloudBlob blob, IAsyncResult result)
        {
            if (TransferType == TransferTypeEnum.Upload)
                blob.EndUploadFromStream(result);
        }
        private void BlobTransferCompletedCallback(IAsyncResult result)
        {
            BlobTransferAsyncState state = (BlobTransferAsyncState)result.AsyncState;
            ICloudBlob blob = state.Blob;
            ProgressStream stream = (ProgressStream)state.Stream;

            try
            {
                stream.Close();
                fileOpened = false;
                EnsureEndUplaod(blob, result);
                EnsureEndDownload(blob, result);
               
                // Operation completed normally, raise the completed event
                AsyncCompletedEventArgs completedArgs = new AsyncCompletedEventArgs(null, false, null);
                asyncOp.PostOperationCompleted(delegate(object e) { OnTaskCompleted((AsyncCompletedEventArgs)e); }, completedArgs);
            }
            catch (StorageException ex)
            {
                if (!state.Cancelled)
                {
                    throw (ex);
                }

                // Operation was cancelled, raise the event with the cancelled flag = true
                AsyncCompletedEventArgs completedArgs = new AsyncCompletedEventArgs(null, true, null);
                asyncOp.PostOperationCompleted(delegate(object e) { OnTaskCompleted((AsyncCompletedEventArgs)e); }, completedArgs);
            }
        }

        // Cancel the async download
        public void CancelAsync()
        {
            ((BlobTransferAsyncState)asyncresult.AsyncState).Cancelled = true;
            asyncresult.Cancel();
        }

        // Helper function to only raise the event if the client has subscribed to it.
        protected virtual void OnTaskCompleted(AsyncCompletedEventArgs e)
        {
            if (TransferCompleted != null)
                TransferCompleted(this, e);
        }

        // Helper function to only raise the event if the client has subscribed to it.
        protected virtual void OnTaskProgressChanged(BlobTransferProgressChangedEventArgs e)
        {
            if (TransferProgressChanged != null)
                TransferProgressChanged(this, e);
        }

        // Keep the last 200 progress change notifications and use them to calculate the average speed over that duration. 
        private double CalculateSpeed(long BytesSent)
        {
            double speed = 0;

            if (timeQueue.Count >= 200)
            {
                timeQueue.Dequeue();
                bytesQueue.Dequeue();
            }

            timeQueue.Enqueue(System.DateTime.Now.Ticks);
            bytesQueue.Enqueue(BytesSent);

            if (timeQueue.Count > 2)
            {
                updateTime = System.DateTime.Now;
                speed = (bytesQueue.Max() - bytesQueue.Min()) / TimeSpan.FromTicks(timeQueue.Max() - timeQueue.Min()).TotalSeconds;
            }

            return speed;
        }
        protected void WriteBlobList(CloudBlobContainer root)
        {
            Results.WriteLine("[{0}]", root.Name);
            Results.WriteLine("[{0}]", "".PadRight(root.Name.Length, '_'));
            var blobs = root.ListBlobs().ToList();
            for (int i = 0; i < blobs.Count(); i++)
            {
                var blob = blobs[i];
                Results.WriteLine(string.Format("  |__ {0}. {1}", i, blob.Uri));
            }
        }
        ~BlobStorageCommand()
        {
            // Any mishapps not closing stream....
            if(fileStream != null)
            {
                try
                {
                    if (fileOpened)
                    {
                        fileStream.Close();
                        fileStream = null;
                    }
                }
                catch { 
                }
            }
        }
    }
}
